In this notebook, we try to visualize the automatic occupancy detection algorithm used in the RMV2.0 time-of-week temperature (TOWT) model. This article does not promote use of the algorithm for all cases. You should determine whether it is appropriate for your building and usage history.

Intro

The occupancy detection is done by findOccUnocc(). Comments in the code explain the proccess:

Define ‘occupied’ and ‘unoccupied’ based on a regression of load on outdoor temperature: times of week that the regression usually underpredicts the load will be called ‘occupied’, the rest are ‘unoccupied’ This is not foolproof but usually works well.
If the regression underpredicts the load more than 65% of the time then assume it’s an occupied period.

Some details are important to clarify:

Here is the call stack:

Some development work

Can we recover enough data to show the process, from a saved project file? If not, at what point in the code do we need to store some more data?

library(dplyr)
library(glue)
library(RMV2.0)
# The 'project' file saved when you used the RMV2.0 add-in.
rds_file <- "C:/RMV2.0 Workshop/something/Project_02.19.rds"
Project <- readRDS(rds_file)
i = 1

What next? Here, we recreate the function call to towt_baseline.


pre_Data_i <- Project$Data_pre[[i]]
timescaleDays <- Project$model_obj_list$models_list[[i]]$towt_model$timescaleDays
intervalMinutes <- Project$Data_pre_summary[1,6]
fahrenheit <- Project$fahrenheit

res_baseline <- towt_baseline(train_Data = pre_Data_i,
                              pred_Data = pre_Data_i,
                              timescaleDays = timescaleDays,
                              intervalMinutes = intervalMinutes,
                              fahrenheit = fahrenheit,
                              )
Loading required package: shiny
For time weights, tcenter =  2006-01-01 01:00:00 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
For time weights, tcenter =  2006-03-15 01:00:00 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
For time weights, tcenter =  2006-05-27 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
For time weights, tcenter =  2006-08-08 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
For time weights, tcenter =  2006-10-19 23:00:00 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
For time weights, tcenter =  2006-12-31 23:00:00 MST
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667

Now, to illustrate some of the data structures used in the next step …

train <- Project$model_obj_list$models_list[[i]]$train
train
train$time <- as.POSIXlt(train$time, format="%m/%d/%y %H:%M")
head(train$time)
[1] "2006-01-01 01:00:00 MST" "2006-01-01 02:00:00 MST" "2006-01-01 03:00:00 MST"
[4] "2006-01-01 04:00:00 MST" "2006-01-01 05:00:00 MST" "2006-01-01 06:00:00 MST"
pred <- Project$model_obj_list$models_list[[i]]$pred
pred
pred$time <- as.POSIXlt(pred$time, format="%m/%d/%y %H:%M")
head(pred$time)
[1] "2006-01-01 01:00:00 MST" "2006-01-01 02:00:00 MST" "2006-01-01 03:00:00 MST"
[4] "2006-01-01 04:00:00 MST" "2006-01-01 05:00:00 MST" "2006-01-01 06:00:00 MST"

What next? Here, we recreate the function call to makeBaseline. The function loops over a list of timestamps spaced by timescaleDays (the hyperparameter set by the user in the GUI, from 15 to 90 days). At each step, it sets up weights centered at the timestamp and calls fitLBNLregress to create a mini-regression. It then bundles all these regressions together.

verbosity=5
towt_model <- makeBaseline(train$time,
                             train$eload,
                             train$Temp,
                             pred$time,
                             pred$Temp,
                             intervalMinutes=intervalMinutes,
                             timescaleDays=timescaleDays,
                             fahrenheit=fahrenheit,
                             verbose=verbosity)
[1] "starting makeBaseline()"
[1] "running regression at 6 steps"
[1] "starting model run number 1"
For time weights, tcenter =  2006-01-01 01:00:00 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "starting model run number 2"
For time weights, tcenter =  2006-03-15 01:00:00 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "starting model run number 3"
For time weights, tcenter =  2006-05-27 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "starting model run number 4"
For time weights, tcenter =  2006-08-08 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "starting model run number 5"
For time weights, tcenter =  2006-10-19 23:00:00 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "starting model run number 6"
For time weights, tcenter =  2006-12-31 23:00:00 MST
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"
[1] "leaving makeBaseline()"

What next? Here, we recreate the function call to fitLBNLregress.

dataTime <- train$time
dataLoad <- train$eload
dataTemp <- train$Temp
predTime <- pred$time
predTemp <- pred$Temp

tempKnots = (c(40, 55, 65, 80, 90)-32)*5/9
doTemperatureModel<-T
verbose<-5

npoints = length(dataLoad)
t0 = min(dataTime,na.rm=T)
t1 = max(dataTime,na.rm=T)
deltaT = as.numeric(difftime(t1,t0,units="days"))
nsegments = max(1,ceiling(deltaT/timescaleDays))
segmentwidth = (npoints-1)/nsegments
pointlist = floor(sort(npoints-segmentwidth*(0:nsegments))+0.001)
nModelRuns = max(1,length(pointlist))

#for (irun in 1:nModelRuns)
irun <- 1
tcenter = dataTime[pointlist[irun]]
tDiff = as.numeric(difftime(tcenter,dataTime,units="days"))
tDiffPred = as.numeric(difftime(tcenter,predTime,units="days"))
weightvec = timescaleDays^2/(timescaleDays^2 + tDiff^2)

regOut = fitLBNLregress(dataTime, dataLoad, dataTemp, predTime, predTemp,
            tempKnots = tempKnots, weightvec=weightvec,
            intervalMinutes=intervalMinutes,fahrenheit=fahrenheit,
            doTemperatureModel=doTemperatureModel,verbose=verbose)
[1] "starting fitLBNLregress()"
[1] "done determining occupied hours"
In fitLBNLregress, reduced tempKnots:
[1]  4.444444 12.777778 18.333333 26.666667
[1] "done setting up temperature matrix"
[1] "fitting regression for occupied periods"
[1] "done with prediction for occupied periods"
[1] "fitting regression for unoccupied periods"
[1] "done with prediction for unoccupied periods"
[1] "leaving fitLBNLregress()"

What next? Here, we recreate the function call to findOccUnocc (from the first step inside fitLBNLregress). Note:

timeVec <- dataTime
loadVec <- dataLoad
tempVec <- dataTemp
#predTime
#predTemp
#tempKnots
#weightvec
#intervalMinutes
#fahrenheit
#doTemperatureModel
#verbose

minuteOfWeek = 24*60*timeVec$wday+60*timeVec$hour + timeVec$min
intervalOfWeek = 1+floor(minuteOfWeek/intervalMinutes)

# If we have temperature data then fit the time-of-week-and-temperature model

if (fahrenheit) {
    # temperature vector is already in fahrenheit
    tempVecF = tempVec
    tempVec = (tempVec-32)*5/9
    tempVecPredF = predTemp
    tempVecPred = (predTemp-32)*5/9
} else {
    tempVecF = (tempVec*9/5)+32
    tempVecPredF = (predTemp*9/5)+32
    tempVecPred = predTemp
}

# findOccUnocc requires Fahrenheit temperatures; everywhere else we can use either
#  Celsius or Fahrenheit, as long as temperature knots are set appropriately
#
# base occupied/unoccupied decision only on cases where we have load data:
okload = !is.na(loadVec)
occInfo = findOccUnocc(intervalOfWeek[okload],loadVec[okload],tempVecF[okload])
head(occInfo,40)
      uTOW okocc
 [1,]    2     0
 [2,]    3     0
 [3,]    4     0
 [4,]    5     0
 [5,]    6     0
 [6,]    7     0
 [7,]    8     0
 [8,]    9     0
 [9,]   10     0
[10,]   11     0
[11,]   12     0
[12,]   13     0
[13,]   14     0
[14,]   15     0
[15,]   16     0
[16,]   17     0
[17,]   18     0
[18,]   19     0
[19,]   20     0
[20,]   21     0
[21,]   22     0
[22,]   23     0
[23,]   24     0
[24,]   25     0
[25,]   26     0
[26,]   27     0
[27,]   28     0
[28,]   29     0
[29,]   30     0
[30,]   31     0
[31,]   32     0
[32,]   33     1
[33,]   34     1
[34,]   35     1
[35,]   36     1
[36,]   37     1
[37,]   38     1
[38,]   39     1
[39,]   40     1
[40,]   41     1
tail(occInfo,20)
       uTOW okocc
[149,]  150     0
[150,]  151     0
[151,]  152     0
[152,]  153     1
[153,]  154     1
[154,]  155     1
[155,]  156     1
[156,]  157     1
[157,]  158     1
[158,]  159     0
[159,]  160     0
[160,]  161     0
[161,]  162     0
[162,]  163     0
[163,]  164     0
[164,]  165     0
[165,]  166     0
[166,]  167     0
[167,]  168     0
[168,]    1     0

What next? Here, we demonstrate the occupancy detection algorithm within findOccUnocc. Note:

intervalOfWeek2 <- intervalOfWeek[okload]
loadVec2 <- loadVec[okload]
TempF <- tempVecF[okload]
#intervalMinutes
#verbose

# Figure out which times of week a building is in one of two modes
#  (called 'occupied' or 'unoccupied')

uTOW = unique(intervalOfWeek2)
nTOW = length(uTOW)

# Define 'occupied' and 'unoccupied' based on a regression
# of load on outdoor temperature: times of week that the regression usually
# underpredicts the load will be called 'occupied', the rest are 'unoccupied'
# This is not foolproof but usually works well.
#
TempF50 = TempF-50
TempF50[TempF > 50] = 0
TempF65 = TempF-65
TempF65[TempF < 65] = 0

if (verbose > 4) {
    cat("Fitting temperature regression...\n")
}
Fitting temperature regression...
amod = lm(loadVec2 ~ TempF50+TempF65,na.action=na.exclude)

okocc = rep(0,nTOW)
cat("Detecting occupancy ...\n")
Detecting occupancy ...
for (itow in 1:nTOW) {
    okTOW = intervalOfWeek2==uTOW[itow]
    # if the regression underpredicts the load more than 65% of the time
    # then assume it's an occupied period
    if ( sum(residuals(amod)[okTOW]>0,na.rm=T) > 0.65*sum(okTOW) ) {
        okocc[itow]=1
    }
  cat(glue('[{format(t,width=3)}] {format(nunder,width=2)}/{format(ntotal,width=2)} -> {occ}',
           t=uTOW[itow],
           nunder=sum(residuals(amod)[okTOW]>0,na.rm=T),
           ntotal=sum(okTOW),
           occ=okocc[itow]
           ),'\n')

}
[  2]  0/53 -> 0 
[  3]  0/52 -> 0 
[  4]  0/53 -> 0 
[  5]  0/53 -> 0 
[  6]  0/53 -> 0 
[  7]  0/53 -> 0 
[  8]  0/53 -> 0 
[  9]  0/53 -> 0 
[ 10]  0/53 -> 0 
[ 11]  0/53 -> 0 
[ 12]  0/53 -> 0 
[ 13]  0/53 -> 0 
[ 14]  0/53 -> 0 
[ 15]  0/53 -> 0 
[ 16]  0/53 -> 0 
[ 17]  0/53 -> 0 
[ 18]  0/53 -> 0 
[ 19]  0/53 -> 0 
[ 20]  0/53 -> 0 
[ 21]  0/53 -> 0 
[ 22]  0/53 -> 0 
[ 23]  0/53 -> 0 
[ 24]  0/53 -> 0 
[ 25]  0/52 -> 0 
[ 26]  0/52 -> 0 
[ 27]  0/52 -> 0 
[ 28]  0/52 -> 0 
[ 29]  0/52 -> 0 
[ 30]  0/52 -> 0 
[ 31]  0/52 -> 0 
[ 32] 12/52 -> 0 
[ 33] 40/52 -> 1 
[ 34] 46/52 -> 1 
[ 35] 46/52 -> 1 
[ 36] 46/52 -> 1 
[ 37] 46/52 -> 1 
[ 38] 46/52 -> 1 
[ 39] 46/52 -> 1 
[ 40] 46/52 -> 1 
[ 41] 46/52 -> 1 
[ 42] 46/52 -> 1 
[ 43] 43/52 -> 1 
[ 44] 44/52 -> 1 
[ 45] 35/52 -> 1 
[ 46] 32/52 -> 0 
[ 47] 10/52 -> 0 
[ 48]  0/52 -> 0 
[ 49]  0/52 -> 0 
[ 50]  0/52 -> 0 
[ 51]  0/52 -> 0 
[ 52]  0/52 -> 0 
[ 53]  0/52 -> 0 
[ 54]  0/52 -> 0 
[ 55]  0/52 -> 0 
[ 56]  7/52 -> 0 
[ 57] 42/52 -> 1 
[ 58] 51/52 -> 1 
[ 59] 51/52 -> 1 
[ 60] 51/52 -> 1 
[ 61] 51/52 -> 1 
[ 62] 51/52 -> 1 
[ 63] 51/52 -> 1 
[ 64] 51/52 -> 1 
[ 65] 51/52 -> 1 
[ 66] 51/52 -> 1 
[ 67] 49/52 -> 1 
[ 68] 49/52 -> 1 
[ 69] 42/52 -> 1 
[ 70] 43/52 -> 1 
[ 71] 15/52 -> 0 
[ 72]  0/52 -> 0 
[ 73]  0/52 -> 0 
[ 74]  0/52 -> 0 
[ 75]  0/52 -> 0 
[ 76]  0/52 -> 0 
[ 77]  0/52 -> 0 
[ 78]  0/52 -> 0 
[ 79]  0/52 -> 0 
[ 80]  8/52 -> 0 
[ 81] 47/52 -> 1 
[ 82] 52/52 -> 1 
[ 83] 52/52 -> 1 
[ 84] 52/52 -> 1 
[ 85] 52/52 -> 1 
[ 86] 52/52 -> 1 
[ 87] 52/52 -> 1 
[ 88] 52/52 -> 1 
[ 89] 52/52 -> 1 
[ 90] 52/52 -> 1 
[ 91] 52/52 -> 1 
[ 92] 52/52 -> 1 
[ 93] 39/52 -> 1 
[ 94] 38/52 -> 1 
[ 95] 14/52 -> 0 
[ 96]  0/52 -> 0 
[ 97]  0/52 -> 0 
[ 98]  0/52 -> 0 
[ 99]  0/52 -> 0 
[100]  0/52 -> 0 
[101]  0/52 -> 0 
[102]  0/52 -> 0 
[103]  0/52 -> 0 
[104]  8/52 -> 0 
[105] 44/52 -> 1 
[106] 51/52 -> 1 
[107] 51/52 -> 1 
[108] 51/52 -> 1 
[109] 51/52 -> 1 
[110] 51/52 -> 1 
[111] 51/52 -> 1 
[112] 51/52 -> 1 
[113] 51/52 -> 1 
[114] 51/52 -> 1 
[115] 50/52 -> 1 
[116] 51/52 -> 1 
[117] 43/52 -> 1 
[118] 38/52 -> 1 
[119] 14/52 -> 0 
[120]  0/52 -> 0 
[121]  0/52 -> 0 
[122]  0/52 -> 0 
[123]  0/52 -> 0 
[124]  0/52 -> 0 
[125]  0/52 -> 0 
[126]  0/52 -> 0 
[127]  0/52 -> 0 
[128] 10/52 -> 0 
[129] 46/52 -> 1 
[130] 52/52 -> 1 
[131] 52/52 -> 1 
[132] 52/52 -> 1 
[133] 52/52 -> 1 
[134] 52/52 -> 1 
[135] 52/52 -> 1 
[136] 52/52 -> 1 
[137] 52/52 -> 1 
[138] 52/52 -> 1 
[139] 50/52 -> 1 
[140] 52/52 -> 1 
[141] 42/52 -> 1 
[142] 35/52 -> 1 
[143] 11/52 -> 0 
[144]  0/52 -> 0 
[145]  0/52 -> 0 
[146]  0/52 -> 0 
[147]  0/52 -> 0 
[148]  0/52 -> 0 
[149]  0/52 -> 0 
[150]  0/52 -> 0 
[151]  0/52 -> 0 
[152]  0/52 -> 0 
[153] 34/52 -> 1 
[154] 50/52 -> 1 
[155] 45/52 -> 1 
[156] 47/52 -> 1 
[157] 43/52 -> 1 
[158] 37/52 -> 1 
[159] 14/52 -> 0 
[160]  0/52 -> 0 
[161]  0/52 -> 0 
[162]  0/52 -> 0 
[163]  0/52 -> 0 
[164]  0/52 -> 0 
[165]  0/52 -> 0 
[166]  0/52 -> 0 
[167]  0/52 -> 0 
[168]  0/52 -> 0 
[  1]  0/52 -> 0 
occInfo = cbind(uTOW,okocc)

Visualization

Here are a bunch of ways to visualize the occupancy detection algorithm. The best one is all the way at the bottom, which is mostly scripted in Javascript with Charts.js.

Static plot using ggplot2

library(ggplot2)
tow <- 40
okTOW = intervalOfWeek2==tow

ggplot() +
  geom_point(data=data.frame(x=TempF,y=loadVec2), aes(x, y), color='gray') +
  geom_point(data=data.frame(x=TempF[okTOW],y=loadVec2[okTOW]), aes(x, y), color='blue') +
  geom_line(data=data.frame(x=TempF,y=predict(amod)), aes(x, y)) +
  xlab("Temperature (°F)") + #"\xB0"
  ylab("Load (kW)")

A bunch of static plots

# To repeat the same data in every panel, simply construct a data frame
# that does not contain the faceting variable.
mydata=data.frame(x=TempF,y=loadVec2,ypred=predict(amod),tow=intervalOfWeek2)
mydata[mydata$tow<10,]
ggplot(mydata[mydata$tow<10,], aes(x, y)) +
  geom_point(data = transform(mydata, tow = NULL), colour = "grey85") +
  geom_point() +
  facet_wrap(vars(tow))

Animated plot using plotly

library(plotly)

Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout
mydata %>%
  plot_ly(
    x = ~x,
    y = ~y,
    frame = ~tow,
    type = 'scatter',
    mode = 'markers',
    showlegend = F
  )

Using plotly to animate ggplot2 style

#  geom_point(aes(x, y), color='gray') +
#  geom_line(aes(x, ypred)) +

p <- ggplot(mydata) +
  geom_point(aes(x, y, frame=tow), color='blue') +
  xlab("Temperature (°F)") + #"\xB0"
  ylab("Load (kW)")
Ignoring unknown aesthetics: frame
fig <- ggplotly(p) %>%
  animation_opts(
    frame=500, easing='linear', redraw = F
  )
fig

Plotly animation WITH full point cloud in the background

This suffers from slow speed (heavy on the CPU usage).

mydata %>%
  plot_ly(x=~x,mode='none') %>%
  layout(xaxis=list(title=list(text='Temperature (°F)')),
         yaxis=list(title=list(text='Load (kW)'))) %>%
  add_trace(y=~y,name='loads', type='scatter', mode='markers',
            showlegend=F, marker=list(color='grey'), text = 'none') %>%
  add_lines(y=~ypred,name='fit',
            showlegend=F, line=list(color = 'black')) %>%
  add_trace(
    y = ~y,
    frame = ~tow,
    type = 'scatter',
    mode = 'markers',
    showlegend = F,
    marker = list(color = 'blue')
  ) %>%
  animation_opts(
    frame=500, easing='linear', redraw=F
  )

Customized widget with Chart.js

Next, I’ve created a javascript-based charting widget because this is more interactive than an animation, and works better because it doesn’t animate the point cloud in the background. The widget does not yet interact with R, so I’m just going to show how to export some data to a file that I have manually pasted into the widget. Here are some future features:

  • Generate the widget from the R markdown notebook automatically.
  • Add a slider for the detection threshold (now at 65%).
  • Caution: the time-of-week numbers here may not be the same as in the R script.

Here is how I export the data to the widget.

# Convert to JSON for Chart.js
dataByTOW=uTOW %>% purrr::map(~ mydata[mydata$tow==.x,])
dataByTOWjson = jsonlite::toJSON(dataByTOW, pretty=F)
#fitLine = unique(mydata[with(mydata,order(x)),c('x','ypred')])
fitLine = data.frame(x=TempF,y=predict(amod)) %>% unique() %>% arrange(x)
fitLinejson = jsonlite::toJSON(fitLine, pretty=F)
sink('datafile.js')
cat(glue('const dataByTOW = {dataByTOWjson};
const fitLine = {fitLinejson};'))
sink()
#mydata %>% mutate(x=x,y=ypred)

Here is the widget. Try pointing the mouse over the time-of-week grid:

  • Vertical: from Sunday through Saturday
  • Horizontal: from midnight to midnight
  • Collapsed on: week of the year

That is all. Thank you for reading. Find more from my fork of RMV2.0

LS0tCnRpdGxlOiAiT2NjdXBhbmN5IGRldGVjdGlvbiBpbiBSTVYyLjAgVE9XVCBtb2RlbCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSW4gdGhpcyBub3RlYm9vaywgd2UgdHJ5IHRvIHZpc3VhbGl6ZSB0aGUgYXV0b21hdGljIG9jY3VwYW5jeSBkZXRlY3Rpb24KYWxnb3JpdGhtIHVzZWQgaW4gdGhlIFJNVjIuMCB0aW1lLW9mLXdlZWsgdGVtcGVyYXR1cmUgKFRPV1QpIG1vZGVsLiBUaGlzCmFydGljbGUgZG9lcyBub3QgcHJvbW90ZSB1c2Ugb2YgdGhlIGFsZ29yaXRobSBmb3IgYWxsIGNhc2VzLiBZb3Ugc2hvdWxkCmRldGVybWluZSB3aGV0aGVyIGl0IGlzIGFwcHJvcHJpYXRlIGZvciB5b3VyIGJ1aWxkaW5nIGFuZCB1c2FnZSBoaXN0b3J5LgoKIyBJbnRybwoKVGhlIG9jY3VwYW5jeSBkZXRlY3Rpb24gaXMgZG9uZSBieSBgZmluZE9jY1Vub2NjKClgLiBDb21tZW50cyBpbiB0aGUgY29kZQpleHBsYWluIHRoZSBwcm9jY2VzczoKCjxibG9ja3F1b3RlPgpEZWZpbmUgJ29jY3VwaWVkJyBhbmQgJ3Vub2NjdXBpZWQnIGJhc2VkIG9uIGEgcmVncmVzc2lvbgpvZiBsb2FkIG9uIG91dGRvb3IgdGVtcGVyYXR1cmU6IHRpbWVzIG9mIHdlZWsgdGhhdCB0aGUgcmVncmVzc2lvbiB1c3VhbGx5CnVuZGVycHJlZGljdHMgdGhlIGxvYWQgd2lsbCBiZSBjYWxsZWQgJ29jY3VwaWVkJywgdGhlIHJlc3QgYXJlICd1bm9jY3VwaWVkJwpUaGlzIGlzIG5vdCBmb29scHJvb2YgYnV0IHVzdWFsbHkgd29ya3Mgd2VsbC4KPC9ibG9ja3F1b3RlPgo8YmxvY2txdW90ZT4KSWYgdGhlIHJlZ3Jlc3Npb24gdW5kZXJwcmVkaWN0cyB0aGUgbG9hZCBtb3JlIHRoYW4gNjUlIG9mIHRoZSB0aW1lCnRoZW4gYXNzdW1lIGl0J3MgYW4gb2NjdXBpZWQgcGVyaW9kLgo8L2Jsb2NrcXVvdGU+CgpTb21lIGRldGFpbHMgYXJlIGltcG9ydGFudCB0byBjbGFyaWZ5OgoKKiBUaGVzZSByZWdyZXNzaW9uIGZpdCB1c2VkIGhlcmUgaXMgdGhyb3duIGF3YXkgYWZ0ZXIgb2NjdXBhbmN5IGRldGVjdGlvbi4gSXQKICBpcyBub3QgcmVsYXRlZCB0byB0aGUgYmFzZWxpbmUgbW9kZWwgd2UgZXZlbnR1YWxseSBnZXQuCiogV2UgdXNlIGEgdHdvLWNoYW5nZXBvaW50IG1vZGVsIHRvIGZpdCB0aGUgOCw3NjAtaG91ciBkYXRhIGFnYWluc3QgdGVtcGVyYXR1cmUsCiAgYW5kIG5vIGFkZGl0aW9uYWwgdmFyaWFibGVzLiAKKiBfVW5kZXJwcmVkaWN0XyBtZWFucyByZXNpZHVhbCA+IDAsIHdoZXJlIHJlc2lkdWFsID0gZGF0YSAtIHJlZ3Jlc3Npb24gcHJlZGljdGlvbi4KKiBfNjUlIG9mIHRoZSB0aW1lXzogQXQgdGhpcyBwb2ludCwgd2UgZ3JvdXAgZGF0YSBwb2ludHMgYnkgdGltZSBvZiB3ZWVrLCBzbwogIHRoZXJlIGFyZSA3KjI0ID0gMTY4IGdyb3Vwcy4gRm9yIGVhY2ggZ3JvdXAsIHdlIHRoZW4gY2FsY3VsYXRlIHRoZSBudW1iZXIgb2YKICBkYXRhIHBvaW50cyAoMSBwZXIgd2VlaywgdXN1YWxseSA1MiB0b3RhbCkgYW5kIG9mIHRob3NlLCB0aGUgbnVtYmVyIG9mIGRhdGEKICBwb2ludHMgd2hlcmUgdGhlIHJlZ3Jlc3Npb24gdW5kZXJwcmVkaWN0cy4gSWYgdGhlIHJhdGlvIGlzIGdyZWF0ZXIgdGhhbiA2NSUKICAoYW4gYXJiaXRyYXJ5IHRocmVzaG9sZCksIHdlIHNldCB0aGlzIHRpbWUgb2Ygd2VlayBhcyAib2NjdXBpZWQiLgoKSGVyZSBpcyB0aGUgY2FsbCBzdGFjazoKCiogYHRyYWluX21vZGVsYCAoY2FsbGVkIGF0IDx0dD5zZXJ2ZXIuUiMyOTg8L3R0PiwgZGVmaW5lZCBhdCA8dHQ+dXRpbHMuUiMyMTU8L3R0PikKKiBgdG93dF9iYXNlbGluZWAgKGNhbGxlZCBhdCA8dHQ+dXRpbHMuUiMyNDM8L3R0PiwgZGVmaW5lZCBhdAogIDx0dD50b3d0X2Jhc2VsaW5lLlIjNDI8L3R0PikKKiBgbWFrZUJhc2VsaW5lYCAoY2FsbGVkIGF0IDx0dD50b3d0X2Jhc2VsaW5lLlIjNzQ8L3R0PiwgZGVmaW5lZCBhdAogIDx0dD50b3d0X2Jhc2VsaW5lLlIjNDMzPC90dD4pCiogYGZpdExCTkxyZWdyZXNzYCAoY2FsbGVkIGF0IDx0dD50b3d0X2Jhc2VsaW5lLlIjNDkxPC90dD4sIGRlZmluZWQgYXQKICA8dHQ+dG93dF9iYXNlbGluZS5SIzIyNDwvdHQ+KQoqIGBmaW5kT2NjVW5vY2NgIChjYWxsZWQgYXQgPHR0PnRvd3RfYmFzZWxpbmUuUiMyOTY8L3R0PiwgZGVmaW5lZCBhdAogIDx0dD50b3d0X2Jhc2VsaW5lLlIjMTg2PC90dD4pCgojIFNvbWUgZGV2ZWxvcG1lbnQgd29yawoKQ2FuIHdlIHJlY292ZXIgZW5vdWdoIGRhdGEgdG8gc2hvdyB0aGUgcHJvY2VzcywgZnJvbSBhIHNhdmVkIHByb2plY3QgZmlsZT8gSWYKbm90LCBhdCB3aGF0IHBvaW50IGluIHRoZSBjb2RlIGRvIHdlIG5lZWQgdG8gc3RvcmUgc29tZSBtb3JlIGRhdGE/CgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnbHVlKQpsaWJyYXJ5KFJNVjIuMCkKYGBgCgpgYGB7cn0KIyBUaGUgJ3Byb2plY3QnIGZpbGUgc2F2ZWQgd2hlbiB5b3UgdXNlZCB0aGUgUk1WMi4wIGFkZC1pbi4KcmRzX2ZpbGUgPC0gIkM6L1JNVjIuMCBXb3Jrc2hvcC9zb21ldGhpbmcvUHJvamVjdF8wMi4xOS5yZHMiClByb2plY3QgPC0gcmVhZFJEUyhyZHNfZmlsZSkKaSA9IDEKYGBgCgpXaGF0IG5leHQ/IEhlcmUsIHdlIHJlY3JlYXRlIHRoZSBmdW5jdGlvbiBjYWxsIHRvIGB0b3d0X2Jhc2VsaW5lYC4KCmBgYHtyfQoKcHJlX0RhdGFfaSA8LSBQcm9qZWN0JERhdGFfcHJlW1tpXV0KdGltZXNjYWxlRGF5cyA8LSBQcm9qZWN0JG1vZGVsX29ial9saXN0JG1vZGVsc19saXN0W1tpXV0kdG93dF9tb2RlbCR0aW1lc2NhbGVEYXlzCmludGVydmFsTWludXRlcyA8LSBQcm9qZWN0JERhdGFfcHJlX3N1bW1hcnlbMSw2XQpmYWhyZW5oZWl0IDwtIFByb2plY3QkZmFocmVuaGVpdAoKcmVzX2Jhc2VsaW5lIDwtIHRvd3RfYmFzZWxpbmUodHJhaW5fRGF0YSA9IHByZV9EYXRhX2ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRfRGF0YSA9IHByZV9EYXRhX2ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzY2FsZURheXMgPSB0aW1lc2NhbGVEYXlzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcnZhbE1pbnV0ZXMgPSBpbnRlcnZhbE1pbnV0ZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhaHJlbmhlaXQgPSBmYWhyZW5oZWl0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmBgYAoKTm93LCB0byBpbGx1c3RyYXRlIHNvbWUgb2YgdGhlIGRhdGEgc3RydWN0dXJlcyB1c2VkIGluIHRoZSBuZXh0IHN0ZXAgLi4uCgpgYGB7cn0KdHJhaW4gPC0gUHJvamVjdCRtb2RlbF9vYmpfbGlzdCRtb2RlbHNfbGlzdFtbaV1dJHRyYWluCnRyYWluCmBgYAoKYGBge3J9CnRyYWluJHRpbWUgPC0gYXMuUE9TSVhsdCh0cmFpbiR0aW1lLCBmb3JtYXQ9IiVtLyVkLyV5ICVIOiVNIikKaGVhZCh0cmFpbiR0aW1lKQpgYGAKCmBgYHtyfQpwcmVkIDwtIFByb2plY3QkbW9kZWxfb2JqX2xpc3QkbW9kZWxzX2xpc3RbW2ldXSRwcmVkCnByZWQKYGBgCgpgYGB7cn0KcHJlZCR0aW1lIDwtIGFzLlBPU0lYbHQocHJlZCR0aW1lLCBmb3JtYXQ9IiVtLyVkLyV5ICVIOiVNIikKaGVhZChwcmVkJHRpbWUpCmBgYAoKV2hhdCBuZXh0PyBIZXJlLCB3ZSByZWNyZWF0ZSB0aGUgZnVuY3Rpb24gY2FsbCB0byBgbWFrZUJhc2VsaW5lYC4gVGhlIGZ1bmN0aW9uIGxvb3BzCm92ZXIgYSBsaXN0IG9mIHRpbWVzdGFtcHMgc3BhY2VkIGJ5IHRpbWVzY2FsZURheXMgKHRoZSBoeXBlcnBhcmFtZXRlciBzZXQgYnkgdGhlIHVzZXIKaW4gdGhlIEdVSSwgZnJvbSAxNSB0byA5MCBkYXlzKS4gQXQgZWFjaCBzdGVwLCBpdCBzZXRzIHVwIHdlaWdodHMgY2VudGVyZWQgYXQgdGhlCnRpbWVzdGFtcCBhbmQgY2FsbHMgYGZpdExCTkxyZWdyZXNzYCB0byBjcmVhdGUgYSBtaW5pLXJlZ3Jlc3Npb24uIEl0IHRoZW4gYnVuZGxlcyBhbGwKdGhlc2UgcmVncmVzc2lvbnMgdG9nZXRoZXIuCgpgYGB7cn0KdmVyYm9zaXR5PTUKdG93dF9tb2RlbCA8LSBtYWtlQmFzZWxpbmUodHJhaW4kdGltZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbiRlbG9hZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbiRUZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWQkdGltZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkJFRlbXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW50ZXJ2YWxNaW51dGVzPWludGVydmFsTWludXRlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lc2NhbGVEYXlzPXRpbWVzY2FsZURheXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFocmVuaGVpdD1mYWhyZW5oZWl0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zaXR5KQoKYGBgCgpXaGF0IG5leHQ/IEhlcmUsIHdlIHJlY3JlYXRlIHRoZSBmdW5jdGlvbiBjYWxsIHRvIGBmaXRMQk5McmVncmVzc2AuCgpgYGB7cn0KZGF0YVRpbWUgPC0gdHJhaW4kdGltZQpkYXRhTG9hZCA8LSB0cmFpbiRlbG9hZApkYXRhVGVtcCA8LSB0cmFpbiRUZW1wCnByZWRUaW1lIDwtIHByZWQkdGltZQpwcmVkVGVtcCA8LSBwcmVkJFRlbXAKCnRlbXBLbm90cyA9IChjKDQwLCA1NSwgNjUsIDgwLCA5MCktMzIpKjUvOQpkb1RlbXBlcmF0dXJlTW9kZWw8LVQKdmVyYm9zZTwtNQoKbnBvaW50cyA9IGxlbmd0aChkYXRhTG9hZCkKdDAgPSBtaW4oZGF0YVRpbWUsbmEucm09VCkKdDEgPSBtYXgoZGF0YVRpbWUsbmEucm09VCkKZGVsdGFUID0gYXMubnVtZXJpYyhkaWZmdGltZSh0MSx0MCx1bml0cz0iZGF5cyIpKQpuc2VnbWVudHMgPSBtYXgoMSxjZWlsaW5nKGRlbHRhVC90aW1lc2NhbGVEYXlzKSkKc2VnbWVudHdpZHRoID0gKG5wb2ludHMtMSkvbnNlZ21lbnRzCnBvaW50bGlzdCA9IGZsb29yKHNvcnQobnBvaW50cy1zZWdtZW50d2lkdGgqKDA6bnNlZ21lbnRzKSkrMC4wMDEpCm5Nb2RlbFJ1bnMgPSBtYXgoMSxsZW5ndGgocG9pbnRsaXN0KSkKCiNmb3IgKGlydW4gaW4gMTpuTW9kZWxSdW5zKQppcnVuIDwtIDEKdGNlbnRlciA9IGRhdGFUaW1lW3BvaW50bGlzdFtpcnVuXV0KdERpZmYgPSBhcy5udW1lcmljKGRpZmZ0aW1lKHRjZW50ZXIsZGF0YVRpbWUsdW5pdHM9ImRheXMiKSkKdERpZmZQcmVkID0gYXMubnVtZXJpYyhkaWZmdGltZSh0Y2VudGVyLHByZWRUaW1lLHVuaXRzPSJkYXlzIikpCndlaWdodHZlYyA9IHRpbWVzY2FsZURheXNeMi8odGltZXNjYWxlRGF5c14yICsgdERpZmZeMikKCnJlZ091dCA9IGZpdExCTkxyZWdyZXNzKGRhdGFUaW1lLCBkYXRhTG9hZCwgZGF0YVRlbXAsIHByZWRUaW1lLCBwcmVkVGVtcCwKCQkJdGVtcEtub3RzID0gdGVtcEtub3RzLCB3ZWlnaHR2ZWM9d2VpZ2h0dmVjLAoJCQlpbnRlcnZhbE1pbnV0ZXM9aW50ZXJ2YWxNaW51dGVzLGZhaHJlbmhlaXQ9ZmFocmVuaGVpdCwKCQkJZG9UZW1wZXJhdHVyZU1vZGVsPWRvVGVtcGVyYXR1cmVNb2RlbCx2ZXJib3NlPXZlcmJvc2UpCmBgYAoKV2hhdCBuZXh0PyBIZXJlLCB3ZSByZWNyZWF0ZSB0aGUgZnVuY3Rpb24gY2FsbCB0byBgZmluZE9jY1Vub2NjYCAoZnJvbSB0aGUgZmlyc3Qgc3RlcAppbnNpZGUgYGZpdExCTkxyZWdyZXNzYCkuIE5vdGU6CgoqIFRoaXMgaXMgd2hlcmUgd2UgZmlyc3QgbGFiZWwgZWFjaCB0aW1lc3RhbXAgd2l0aCBhbiBpbnRlZ2VyIGBpbnRlcnZhbE9mV2Vla2AgdXNpbmcgdGhlIHdlZWtkYXksIGhvdXIsIGFuZCBtaW51dGUgcGFydHMgb2YgdGhlIHRpbWVzdGFtcCBkYXRhLgoqIFRoZSBgaW50ZXJ2YWxPZldlZWtgIGluY3JlbWVudHMgYnkgMSBldmVyeSBgaW50ZXJ2YWxNaW51dGVzYCAoaW4gdGhpcyBwcm9qZWN0LCBldmVyeSA2MCBtaW51dGVzKS4KKiBFZy4sIFN1bmRheSwgMjAwNi0wMS0wMVQwMDowMCBoYXMgKHdkYXksIGhvdXIsIG1pbnV0ZSkgPSAoMCwwLDApLiAwMDowMCB0aHJvdWdoIDAwOjU5IHdvdWxkIGFsbCBiZSBsYWJlbGVkIGBpbnRlcnZhbE9mV2Vla2AgPSAxLgoqIEVnLiwgU3VuZGF5LCAyMDA2LTAxLTAxVDAxOjAwIGhhcyAod2RheSwgaG91ciwgbWludXRlKSA9ICgwLDEsMCksIHdoaWNoIGlzIG1hcHBlZCB0byBpbnRlcnZhbE9mV2VlayA9IDIuCgpgYGB7cn0KdGltZVZlYyA8LSBkYXRhVGltZQpsb2FkVmVjIDwtIGRhdGFMb2FkCnRlbXBWZWMgPC0gZGF0YVRlbXAKI3ByZWRUaW1lCiNwcmVkVGVtcAojdGVtcEtub3RzCiN3ZWlnaHR2ZWMKI2ludGVydmFsTWludXRlcwojZmFocmVuaGVpdAojZG9UZW1wZXJhdHVyZU1vZGVsCiN2ZXJib3NlCgptaW51dGVPZldlZWsgPSAyNCo2MCp0aW1lVmVjJHdkYXkrNjAqdGltZVZlYyRob3VyICsgdGltZVZlYyRtaW4KaW50ZXJ2YWxPZldlZWsgPSAxK2Zsb29yKG1pbnV0ZU9mV2Vlay9pbnRlcnZhbE1pbnV0ZXMpCgojIElmIHdlIGhhdmUgdGVtcGVyYXR1cmUgZGF0YSB0aGVuIGZpdCB0aGUgdGltZS1vZi13ZWVrLWFuZC10ZW1wZXJhdHVyZSBtb2RlbAoKaWYgKGZhaHJlbmhlaXQpIHsKCSMgdGVtcGVyYXR1cmUgdmVjdG9yIGlzIGFscmVhZHkgaW4gZmFocmVuaGVpdAoJdGVtcFZlY0YgPSB0ZW1wVmVjCgl0ZW1wVmVjID0gKHRlbXBWZWMtMzIpKjUvOQoJdGVtcFZlY1ByZWRGID0gcHJlZFRlbXAKCXRlbXBWZWNQcmVkID0gKHByZWRUZW1wLTMyKSo1LzkKfSBlbHNlIHsKCXRlbXBWZWNGID0gKHRlbXBWZWMqOS81KSszMgoJdGVtcFZlY1ByZWRGID0gKHByZWRUZW1wKjkvNSkrMzIKCXRlbXBWZWNQcmVkID0gcHJlZFRlbXAKfQoKIyBmaW5kT2NjVW5vY2MgcmVxdWlyZXMgRmFocmVuaGVpdCB0ZW1wZXJhdHVyZXM7IGV2ZXJ5d2hlcmUgZWxzZSB3ZSBjYW4gdXNlIGVpdGhlcgojICBDZWxzaXVzIG9yIEZhaHJlbmhlaXQsIGFzIGxvbmcgYXMgdGVtcGVyYXR1cmUga25vdHMgYXJlIHNldCBhcHByb3ByaWF0ZWx5CiMKIyBiYXNlIG9jY3VwaWVkL3Vub2NjdXBpZWQgZGVjaXNpb24gb25seSBvbiBjYXNlcyB3aGVyZSB3ZSBoYXZlIGxvYWQgZGF0YToKb2tsb2FkID0gIWlzLm5hKGxvYWRWZWMpCm9jY0luZm8gPSBmaW5kT2NjVW5vY2MoaW50ZXJ2YWxPZldlZWtbb2tsb2FkXSxsb2FkVmVjW29rbG9hZF0sdGVtcFZlY0Zbb2tsb2FkXSkKaGVhZChvY2NJbmZvLDQwKQp0YWlsKG9jY0luZm8sMjApCmBgYAoKV2hhdCBuZXh0PyBIZXJlLCB3ZSBkZW1vbnN0cmF0ZSB0aGUgb2NjdXBhbmN5IGRldGVjdGlvbiBhbGdvcml0aG0gd2l0aGluIGBmaW5kT2NjVW5vY2NgLiBOb3RlOgoKKiBgdVRPV2AgPSB1bmlxdWUgdGltZS1vZi13ZWVrLiBJZiBgaW50ZXJ2YWxNaW51dGVzYD02MCwgdGhlbiB0aGVyZSBhcmUgdXAgdG8gMTY4IHN1Y2gKbnVtYmVycy4gV2UgZG8gbm90IG5lZWQgdG8gc29ydCB0aGVtLCB3aGljaCBpcyB1bm5lY2Vzc2FyeS4KKiAKCmBgYHtyfQppbnRlcnZhbE9mV2VlazIgPC0gaW50ZXJ2YWxPZldlZWtbb2tsb2FkXQpsb2FkVmVjMiA8LSBsb2FkVmVjW29rbG9hZF0KVGVtcEYgPC0gdGVtcFZlY0Zbb2tsb2FkXQojaW50ZXJ2YWxNaW51dGVzCiN2ZXJib3NlCgojIEZpZ3VyZSBvdXQgd2hpY2ggdGltZXMgb2Ygd2VlayBhIGJ1aWxkaW5nIGlzIGluIG9uZSBvZiB0d28gbW9kZXMKIyAgKGNhbGxlZCAnb2NjdXBpZWQnIG9yICd1bm9jY3VwaWVkJykKCnVUT1cgPSB1bmlxdWUoaW50ZXJ2YWxPZldlZWsyKQpuVE9XID0gbGVuZ3RoKHVUT1cpCgojIERlZmluZSAnb2NjdXBpZWQnIGFuZCAndW5vY2N1cGllZCcgYmFzZWQgb24gYSByZWdyZXNzaW9uCiMgb2YgbG9hZCBvbiBvdXRkb29yIHRlbXBlcmF0dXJlOiB0aW1lcyBvZiB3ZWVrIHRoYXQgdGhlIHJlZ3Jlc3Npb24gdXN1YWxseQojIHVuZGVycHJlZGljdHMgdGhlIGxvYWQgd2lsbCBiZSBjYWxsZWQgJ29jY3VwaWVkJywgdGhlIHJlc3QgYXJlICd1bm9jY3VwaWVkJwojIFRoaXMgaXMgbm90IGZvb2xwcm9vZiBidXQgdXN1YWxseSB3b3JrcyB3ZWxsLgojClRlbXBGNTAgPSBUZW1wRi01MApUZW1wRjUwW1RlbXBGID4gNTBdID0gMApUZW1wRjY1ID0gVGVtcEYtNjUKVGVtcEY2NVtUZW1wRiA8IDY1XSA9IDAKCmlmICh2ZXJib3NlID4gNCkgewoJY2F0KCJGaXR0aW5nIHRlbXBlcmF0dXJlIHJlZ3Jlc3Npb24uLi5cbiIpCn0KYW1vZCA9IGxtKGxvYWRWZWMyIH4gVGVtcEY1MCtUZW1wRjY1LG5hLmFjdGlvbj1uYS5leGNsdWRlKQoKb2tvY2MgPSByZXAoMCxuVE9XKQpjYXQoIkRldGVjdGluZyBvY2N1cGFuY3kgLi4uXG4iKQpmb3IgKGl0b3cgaW4gMTpuVE9XKSB7Cglva1RPVyA9IGludGVydmFsT2ZXZWVrMj09dVRPV1tpdG93XQoJIyBpZiB0aGUgcmVncmVzc2lvbiB1bmRlcnByZWRpY3RzIHRoZSBsb2FkIG1vcmUgdGhhbiA2NSUgb2YgdGhlIHRpbWUKCSMgdGhlbiBhc3N1bWUgaXQncyBhbiBvY2N1cGllZCBwZXJpb2QKCWlmICggc3VtKHJlc2lkdWFscyhhbW9kKVtva1RPV10+MCxuYS5ybT1UKSA+IDAuNjUqc3VtKG9rVE9XKSApIHsKCQlva29jY1tpdG93XT0xCgl9CiAgY2F0KGdsdWUoJ1t7Zm9ybWF0KHQsd2lkdGg9Myl9XSB7Zm9ybWF0KG51bmRlcix3aWR0aD0yKX0ve2Zvcm1hdChudG90YWwsd2lkdGg9Mil9IC0+IHtvY2N9JywKICAgICAgICAgICB0PXVUT1dbaXRvd10sCiAgICAgICAgICAgbnVuZGVyPXN1bShyZXNpZHVhbHMoYW1vZClbb2tUT1ddPjAsbmEucm09VCksCiAgICAgICAgICAgbnRvdGFsPXN1bShva1RPVyksCiAgICAgICAgICAgb2NjPW9rb2NjW2l0b3ddCiAgICAgICAgICAgKSwnXG4nKQoKfQpvY2NJbmZvID0gY2JpbmQodVRPVyxva29jYykKYGBgCgojIFZpc3VhbGl6YXRpb24KSGVyZSBhcmUgYSBidW5jaCBvZiB3YXlzIHRvIHZpc3VhbGl6ZSB0aGUgb2NjdXBhbmN5IGRldGVjdGlvbiBhbGdvcml0aG0uIFRoZSBiZXN0IG9uZQppcyBhbGwgdGhlIHdheSBhdCB0aGUgYm90dG9tLCB3aGljaCBpcyBtb3N0bHkgc2NyaXB0ZWQgaW4gSmF2YXNjcmlwdCB3aXRoIENoYXJ0cy5qcy4KCiMjIFN0YXRpYyBwbG90IHVzaW5nIGdncGxvdDIKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCnRvdyA8LSA0MApva1RPVyA9IGludGVydmFsT2ZXZWVrMj09dG93CgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhPWRhdGEuZnJhbWUoeD1UZW1wRix5PWxvYWRWZWMyKSwgYWVzKHgsIHkpLCBjb2xvcj0nZ3JheScpICsKICBnZW9tX3BvaW50KGRhdGE9ZGF0YS5mcmFtZSh4PVRlbXBGW29rVE9XXSx5PWxvYWRWZWMyW29rVE9XXSksIGFlcyh4LCB5KSwgY29sb3I9J2JsdWUnKSArCiAgZ2VvbV9saW5lKGRhdGE9ZGF0YS5mcmFtZSh4PVRlbXBGLHk9cHJlZGljdChhbW9kKSksIGFlcyh4LCB5KSkgKwogIHhsYWIoIlRlbXBlcmF0dXJlICjCsEYpIikgKyAjIlx4QjAiCiAgeWxhYigiTG9hZCAoa1cpIikKCmBgYAoKCiMjIEEgYnVuY2ggb2Ygc3RhdGljIHBsb3RzCgpgYGB7cn0KIyBUbyByZXBlYXQgdGhlIHNhbWUgZGF0YSBpbiBldmVyeSBwYW5lbCwgc2ltcGx5IGNvbnN0cnVjdCBhIGRhdGEgZnJhbWUKIyB0aGF0IGRvZXMgbm90IGNvbnRhaW4gdGhlIGZhY2V0aW5nIHZhcmlhYmxlLgpteWRhdGE9ZGF0YS5mcmFtZSh4PVRlbXBGLHk9bG9hZFZlYzIseXByZWQ9cHJlZGljdChhbW9kKSx0b3c9aW50ZXJ2YWxPZldlZWsyKQpteWRhdGFbbXlkYXRhJHRvdzwxMCxdCmBgYAoKYGBge3J9CmdncGxvdChteWRhdGFbbXlkYXRhJHRvdzwxMCxdLCBhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSB0cmFuc2Zvcm0obXlkYXRhLCB0b3cgPSBOVUxMKSwgY29sb3VyID0gImdyZXk4NSIpICsKICBnZW9tX3BvaW50KCkgKwogIGZhY2V0X3dyYXAodmFycyh0b3cpKQpgYGAKCiMjIEFuaW1hdGVkIHBsb3QgdXNpbmcgcGxvdGx5CgpgYGB7cn0KbGlicmFyeShwbG90bHkpCgpteWRhdGEgJT4lCiAgcGxvdF9seSgKICAgIHggPSB+eCwKICAgIHkgPSB+eSwKICAgIGZyYW1lID0gfnRvdywKICAgIHR5cGUgPSAnc2NhdHRlcicsCiAgICBtb2RlID0gJ21hcmtlcnMnLAogICAgc2hvd2xlZ2VuZCA9IEYKICApCmBgYAojIyBVc2luZyBwbG90bHkgdG8gYW5pbWF0ZSBnZ3Bsb3QyIHN0eWxlCgpgYGB7cn0KIyAgZ2VvbV9wb2ludChhZXMoeCwgeSksIGNvbG9yPSdncmF5JykgKwojICBnZW9tX2xpbmUoYWVzKHgsIHlwcmVkKSkgKwoKcCA8LSBnZ3Bsb3QobXlkYXRhKSArCiAgZ2VvbV9wb2ludChhZXMoeCwgeSwgZnJhbWU9dG93KSwgY29sb3I9J2JsdWUnKSArCiAgeGxhYigiVGVtcGVyYXR1cmUgKMKwRikiKSArICMiXHhCMCIKICB5bGFiKCJMb2FkIChrVykiKQoKZmlnIDwtIGdncGxvdGx5KHApICU+JQogIGFuaW1hdGlvbl9vcHRzKAogICAgZnJhbWU9NTAwLCBlYXNpbmc9J2xpbmVhcicsIHJlZHJhdyA9IEYKICApCmZpZwpgYGAKCiMjIFBsb3RseSBhbmltYXRpb24gV0lUSCBmdWxsIHBvaW50IGNsb3VkIGluIHRoZSBiYWNrZ3JvdW5kClRoaXMgc3VmZmVycyBmcm9tIHNsb3cgc3BlZWQgKGhlYXZ5IG9uIHRoZSBDUFUgdXNhZ2UpLgoKYGBge3J9Cm15ZGF0YSAlPiUKICBwbG90X2x5KHg9fngsbW9kZT0nbm9uZScpICU+JQogIGxheW91dCh4YXhpcz1saXN0KHRpdGxlPWxpc3QodGV4dD0nVGVtcGVyYXR1cmUgKMKwRiknKSksCiAgICAgICAgIHlheGlzPWxpc3QodGl0bGU9bGlzdCh0ZXh0PSdMb2FkIChrVyknKSkpICU+JQogIGFkZF90cmFjZSh5PX55LG5hbWU9J2xvYWRzJywgdHlwZT0nc2NhdHRlcicsIG1vZGU9J21hcmtlcnMnLAogICAgICAgICAgICBzaG93bGVnZW5kPUYsIG1hcmtlcj1saXN0KGNvbG9yPSdncmV5JyksIHRleHQgPSAnbm9uZScpICU+JQogIGFkZF9saW5lcyh5PX55cHJlZCxuYW1lPSdmaXQnLAogICAgICAgICAgICBzaG93bGVnZW5kPUYsIGxpbmU9bGlzdChjb2xvciA9ICdibGFjaycpKSAlPiUKICBhZGRfdHJhY2UoCiAgICB5ID0gfnksCiAgICBmcmFtZSA9IH50b3csCiAgICB0eXBlID0gJ3NjYXR0ZXInLAogICAgbW9kZSA9ICdtYXJrZXJzJywKICAgIHNob3dsZWdlbmQgPSBGLAogICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdibHVlJykKICApICU+JQogIGFuaW1hdGlvbl9vcHRzKAogICAgZnJhbWU9NTAwLCBlYXNpbmc9J2xpbmVhcicsIHJlZHJhdz1GCiAgKQpgYGAKCiMjIEN1c3RvbWl6ZWQgd2lkZ2V0IHdpdGggQ2hhcnQuanMKCk5leHQsIEkndmUgY3JlYXRlZCBhIGphdmFzY3JpcHQtYmFzZWQgY2hhcnRpbmcgd2lkZ2V0IGJlY2F1c2UgdGhpcyBpcyBtb3JlCmludGVyYWN0aXZlIHRoYW4gYW4gYW5pbWF0aW9uLCBhbmQgd29ya3MgYmV0dGVyIGJlY2F1c2UgaXQgZG9lc24ndAphbmltYXRlIHRoZSBwb2ludCBjbG91ZCBpbiB0aGUgYmFja2dyb3VuZC4gVGhlIHdpZGdldCBkb2VzIG5vdCB5ZXQgaW50ZXJhY3Qgd2l0aCBSLApzbyBJJ20ganVzdCBnb2luZyB0byBzaG93IGhvdyB0byBleHBvcnQgc29tZSBkYXRhIHRvIGEgZmlsZSB0aGF0IEkgaGF2ZSBtYW51YWxseSBwYXN0ZWQKaW50byB0aGUgd2lkZ2V0LiBIZXJlIGFyZSBzb21lIGZ1dHVyZSBmZWF0dXJlczoKCiogR2VuZXJhdGUgdGhlIHdpZGdldCBmcm9tIHRoZSBSIG1hcmtkb3duIG5vdGVib29rIGF1dG9tYXRpY2FsbHkuCiogQWRkIGEgc2xpZGVyIGZvciB0aGUgZGV0ZWN0aW9uIHRocmVzaG9sZCAobm93IGF0IDY1JSkuCiogQ2F1dGlvbjogdGhlIHRpbWUtb2Ytd2VlayBudW1iZXJzIGhlcmUgbWF5IG5vdCBiZSB0aGUgc2FtZSBhcyBpbiB0aGUgUiBzY3JpcHQuCgpIZXJlIGlzIGhvdyBJIGV4cG9ydCB0aGUgZGF0YSB0byB0aGUgd2lkZ2V0LgoKYGBge3J9CiMgQ29udmVydCB0byBKU09OIGZvciBDaGFydC5qcwpkYXRhQnlUT1c9dVRPVyAlPiUgcHVycnI6Om1hcCh+IG15ZGF0YVtteWRhdGEkdG93PT0ueCxdKQpkYXRhQnlUT1dqc29uID0ganNvbmxpdGU6OnRvSlNPTihkYXRhQnlUT1csIHByZXR0eT1GKQojZml0TGluZSA9IHVuaXF1ZShteWRhdGFbd2l0aChteWRhdGEsb3JkZXIoeCkpLGMoJ3gnLCd5cHJlZCcpXSkKZml0TGluZSA9IGRhdGEuZnJhbWUoeD1UZW1wRix5PXByZWRpY3QoYW1vZCkpICU+JSB1bmlxdWUoKSAlPiUgYXJyYW5nZSh4KQpmaXRMaW5lanNvbiA9IGpzb25saXRlOjp0b0pTT04oZml0TGluZSwgcHJldHR5PUYpCnNpbmsoJ2RhdGFmaWxlLmpzJykKY2F0KGdsdWUoJ2NvbnN0IGRhdGFCeVRPVyA9IHtkYXRhQnlUT1dqc29ufTsKY29uc3QgZml0TGluZSA9IHtmaXRMaW5lanNvbn07JykpCnNpbmsoKQojbXlkYXRhICU+JSBtdXRhdGUoeD14LHk9eXByZWQpCmBgYAoKSGVyZSBpcyB0aGUgd2lkZ2V0LiBUcnkgcG9pbnRpbmcgdGhlIG1vdXNlIG92ZXIgdGhlIHRpbWUtb2Ytd2VlayBncmlkOgoKKiBWZXJ0aWNhbDogZnJvbSBTdW5kYXkgdGhyb3VnaCBTYXR1cmRheQoqIEhvcml6b250YWw6IGZyb20gbWlkbmlnaHQgdG8gbWlkbmlnaHQKKiBDb2xsYXBzZWQgb246IHdlZWsgb2YgdGhlIHllYXIKCjxpZnJhbWUgd2lkdGg9IjkwMCIgaGVpZ2h0PSIxMjAwIiBzcmM9Ii4vbXljaGFydDMuaHRtbCI+PC9pZnJhbWU+CgpUaGF0IGlzIGFsbC4gVGhhbmsgeW91IGZvciByZWFkaW5nLgpGaW5kIG1vcmUgZnJvbSBbbXkgZm9yayBvZiBSTVYyLjBdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaW11bGFyaXMvUk1WMi4wL3RyZWUvZGV2L2RlbW8pCg==